在 TypeScript 中,我們可以使用 type
關鍵字來創建自定義的類型別名。它可以用來定義各種複雜的類型,使我們能夠更好地描述程式碼中的資料結構和變數類型,從而提高程式碼的可讀性和可維護性。威爾豬習慣在類型別名加個前綴字 T
,這樣一看就可以知道是使用 type 這個方式來定義,當然看團隊或個人的習慣。(雖然官方不推薦加上前綴就是~
因為如果使用了前綴,當 type 要改成 interface 時,那所有依賴的地方都要更改,可能會造成不必要的負擔。)
要特別注意的是:每個屬性的結尾是加入「
;
」,而不是「 , 」。
假設我們需要每個員工都有一組 ID 編號和名字:
type TEmployee = {
id: number;
name: string;
};
const employee1: TEmployee = { id: 1, name: "威爾豬" };
const employee2: TEmployee = { id: 2, name: "威爾羊" };
再看另一個例子:
假設我們訂位需要知道顧客名稱和預定人數:
type TOrder = {
name: string;
num: number;
};
const greet = (order: TOrder): string => {
return `您好, ${order.name}! 您的訂位人數是 ${order.num} 位嗎?`;
};
const customer: TOrder = { name: "威爾豬", num: 3 };
console.log(greet(customer)); // 輸出: 您好, 威爾豬! 您的訂位人數是 3 位嗎?
我們也可以結合已經有的類型,創建更複雜的類型。例如:
type TPoint = {
x: number;
y: number;
};
type TCircle = {
center: TPoint;
radius: number;
};
這邊我們使用現有的 TPoint 類型來定義一個 TCircle 類型,它包含一個 center 屬性(是一個 TPoint 類型的物件)和一個 radius 屬性。
假設我們要做一個隨機數字,大於 50 就顯示贏 100 元,小於或等於 50 就顯示 null:
type TWin = string | null;
const num = Math.round(Math.random() * 100);
const maybeWin: TWin = num > 50 ? "贏了 100 元" : null;
console.log(num);
console.log(maybeWin);
我們也可以使用之前介紹的 枚舉值,來創建新的聯合類型別名:
enum Phone {
Apple,
Samsung,
Mi,
}
enum Color {
Gold,
Yellow,
White,
}
type TPhoneOrColor = Phone | Color;
const selectPhone: TPhoneOrColor = Phone.Mi;
const selectColor: TPhoneOrColor = Color.Yellow;
console.log(selectPhone); // 輸出: 2
console.log(selectColor); // 輸出: 1
類型別名可以使用 交叉類型 (&) 的方式讓自定義的類型別名得以擴充。看以下範例:
假設我們已經定義了一個 TPerson 的類型別名,並在其它地方已使用過,但現在有一個地方需要多增加一個地址來使用,我們並不希望重複造輪子,就可以使用擴充的方式來撰寫,看以下範例:
type TPerson = {
name: string;
age: number;
};
type TExtendPerson = TPerson & {
address: string;
email: string;
}; // 擴充 address 和 email 屬性
再來看另一個例子:
原本老闆只要用到 2D,所以我們創建了 TPoint 的類型別名,並已在多個地方快樂使用,結果現在突然有一個要使用 3D:
type TPoint = {
x: number;
y: number;
};
type TPoint3D = TPoint & { z: number }; // 擴充 z 屬性
不過威爾豬通常會使用 可選串連 (?)
的方式來擴充,當然不一樣的情況時也會使用交叉類型的方式,以上述的範例來說:
type TPerson = {
name: string;
age: number;
address?: string; // 可選
email?: string; // 可選
};
type TPoint = {
x: number;
y: number;
z?: number; // 可選
};
這樣不用再額外定義一個新的類型別名,也可以在各個地方通用,程式碼精簡又方便。
我們還可以創建 泛型類型別名,這可以將一個或多個型別參數傳遞給類型別名,以創建在不同型別上使用的泛型結構。
type TGeneric<T> = {
value: T;
};
type TGenerics<T, U> = {
first: T;
second: U;
};
假設我們需要描述一個具有 嵌套結構
的數據,就可以使用遞迴型別別名。
type TNode<T> = {
value: T;
children?: TNode<T>[];
};
const rootNode: TNode<number> = {
value: 1,
children: [
{
value: 2,
children: [
{
value: 3,
children: []
},
]
},
{
value: 4,
children: []
}
]
};
除非我們的結構是像這樣嵌套式的,不然威爾豬非常不建議在其它地方也使用這樣的方式,因為它們會存在一些限制和潛在的問題:
複雜性: 當型別具有深層次的嵌套結構時,這可能會使程式碼變得難以理解和維護。
性能問題: 遞迴類型別名可能導致型別檢查過程變得更加複雜,從而增加 TypeScript 編譯的時間,在某些情況下,過多的遞迴可能會導致性能下降。
不適用於所有情況: 遞迴型別別名主要用於描述嵌套結構的數據,如樹狀結構或深度巢狀的物件。對於扁平結構或單純的型別,就別這樣使用了。 例如:
// 不適合使用遞迴類型別名
type TArray<T> = T[];
const numberArr: TArray<number> = [1, 2, 3, 4, 5];
在這種情況下,直接使用 number[] 就足夠了,沒必要增加不必要的複雜性。
選擇是否使用遞迴類型別名應該基於 專案的數據結構和實際需求
。如果我們的數據具有嵌套結構,而且使用遞迴類型別名能夠使程式碼更清晰和更容易理解,則可以考慮使用它們,但應盡量簡化型別的結構。所以在使用遞迴類型別名之前,應先評估程式碼的需求並考慮 是否有更簡單的方式來表示型別
。
總之,類型別名的主要優勢是增強了程式碼的可讀性和可維護性,使我們能夠為複雜的資料結構創建自訂的類型別名,也可以根據類型別名來檢查變數的類型是否符合預期。不過創建類型別名時,還是要使用有意義的命名,確保每個別名都有實際的價值,也不要濫用而導致程式碼變得更複雜和難以理解。